﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO.Ports;
using JtextEpicsInterface;



    /*****************************************************
     * this is a real driver for epics jecket 
     * this is a serial port driver
     * last update 2010-09-27-1842
     * last update 2011-01-02-1332
     * ********************************************************/
    namespace EJD_ComPort
    {
        public class EJDComPort:IEJDriver
        {
            //this class is used to send and recieve messages from message-based device and DO NOT process it. then hand to EJserver
            //this is also a template to make you own drivers
            //you1don't have to follow the whole structrue
            //for detials and which function is critical look for the document

            const int OUTMSG_TIMEOUT = 50;

            private bool IsByte;    //if the cmd is byte or string
            private int CMDLen; //the lenth of the cmd used in byte cmd
            private byte CMDEnd;    //the end char  of the cmd used in byte cmd

            public delegate void MSGArrivedHandler(byte[] MyMessage);
            MSGArrivedHandler MsgArrived = null;    //listenner will invoke this when message arived


            //call the message Arrived function in EJServer not this class
            //this is decleared in JtextEpicsInterface
            public event SendToEJServerHandler  OnSendToEJServer;
            public event DriverCrashHandler OnDriverCrash;

            public delegate void ListenerHandler();
            ListenerHandler MyListener = null;

            Thread ListenerThread;  //the listener thread

            //async call the out message to the port ,couse you will not know when willit return
            public delegate int OutMSGHandler(byte[] MyMessage);
            OutMSGHandler OutMSG = null;

            private object DrvIniObj;   //the drvier decription

            #region Serial additional Objects
            private object LockFlag;
            private bool hasDataFlag;   //use this two indecate data arrived
            private SerialPort MySPort; //
            string myName = "SerialPort";
            #endregion



            public EJDComPort()
            {
                //constructor
                //thing theshould be done before start driver
                OutMSG = new OutMSGHandler(Out_msg);
                MsgArrived = new MSGArrivedHandler(MessageArrived);
                //SendtoEJServer = SendtoEJS;
                LockFlag = new object();
            }

            //this will set the property of the drivers the first few argument are used to decripe the common proporties
            //that allEJDrivers would have the last argument usually is a string that used to contain Driver specified info like hardware settings
            public void SetDriver(bool isByte, int len, byte end, object IniObj)
            {

                IsByte = isByte;
                CMDLen = len;
                CMDEnd = end;

                //customized code
                hasDataFlag = false;
                if (IniObj == null)
                {
                    //DrvIniObj = new serialDesc();
                    //default driver setting
                    DrvIniObj = "com2;9600;1;0";
                }
                else
                {
                    DrvIniObj = IniObj;
                }
            }

            public void IniDriver()
            {
                //custimized code hear
                string SDs = (string)DrvIniObj;
                //the DrvInfo is a string holds the info in this order
                //port name;baud rate;stop bit;parity
                string[] Sd = Regex.Split(SDs, ";", RegexOptions.None);
                MySPort = new SerialPort(Sd[0],Convert.ToInt16( Sd[1]), ((Parity)Convert.ToInt16(Sd[3])));   //ini and open the serial port
                MySPort.StopBits = ((StopBits)Convert.ToInt16(Sd[2]));
                //serial port specifc code
                MySPort.DataReceived += new SerialDataReceivedEventHandler(DataRecievedHandler);
                if (CMDLen > 0)
                {
                    MySPort.ReceivedBytesThreshold = CMDLen;
                }
                try
                {
                    MySPort.Open();
                    //Console.WriteLine("PortOpen(B201010091546)");
                    Console.WriteLine("PortOpen(B201011301929)");
                }
                catch
                {
                    Console.WriteLine("Serial Port Error");
                    throw new System.Exception("Serial Port Error");
                }

                //customized code end
                try
                {
                    this.StartListener();
                    //Console.WriteLine("PortOpen(B201010091546)");
                }
                catch
                {
                    Console.WriteLine("can not start listenner");
                    throw new System.Exception("can not start listenner");
                }
                
                //start listener here
            }



            public void StopDriver()
            {
                try
                {
                    ListenerThread.Abort();
                    ListenerThread = null;
                    MySPort.Close();
                }
                catch
                {
 
                }
            }

            #region send the message

            public int Send_msg(string message_str)
            {
                byte[] message;
                message = Proc_msg(message_str);
                IAsyncResult ASResult = OutMSG.BeginInvoke(message, null, null);
                if (ASResult.AsyncWaitHandle.WaitOne(OUTMSG_TIMEOUT, false) == true)
                {
                    //use end invoke to get the return value of the outmsg
                    //return 0;
                    return OutMSG.EndInvoke(ASResult);
                }
                else
                {
                    return -1;  //timeout
                }
                //return 0;   //returns the status ,if the message is successfully send out   //called by EJServer
            }

            public int Send_msg(byte[] message_byte)
            {
                byte[] message;
                message = Proc_msg(message_byte);
                IAsyncResult ASResult = OutMSG.BeginInvoke(message, null, null);
                if (ASResult.AsyncWaitHandle.WaitOne(OUTMSG_TIMEOUT, false) == true)
                {
                    return 0;
                }
                else
                {
                    return -1;  //timeout
                }
                //return 0;   //returns the status ,if the message is successfully send out //called by EJServer
            }

            #region simple proc
            //the message used to be done here 
            //now is done by plugin so this only transfer string to byte
            private static byte[] Proc_msg(string messager_str)
            {
                //proccess the message tobe sent
                //test code
                Encoding my_ASC = Encoding.ASCII;
                return my_ASC.GetBytes(messager_str);
            }

            private static byte[] Proc_msg(byte[] messager_byte)
            {
                //proccess the message tobe sent
                //test code
                return messager_byte;
            }
            #endregion

            private int Out_msg(byte[] MyMessage)
            {
                //send the message out via hardware
                //return send status
                //Encoding my_ASC = Encoding.ASCII;
                //string msgStr = my_ASC.GetString(MyMessage);
                //Console.WriteLine(msgStr+msgStr.Length.ToString());
                try
                {
                    if (MySPort.IsOpen == true)
                    {
                        MySPort.Write(MyMessage, 0, MyMessage.Length);
                        //becouse this is run in another thread so others may close the port with out notify this
                    }
                    else
                    {
                        return 0;
                    }
                }
                catch
                {
                    //do nothing
                    return 0;
                }
                Encoding my_ASC = Encoding.ASCII;
                string a = my_ASC.GetString(MyMessage);
                Console.WriteLine("OUT:" + a);
                return 0;
            }


            #endregion

            #region recieve message

            private static byte[] Proc_MSG_In(byte[] myMessage)
            {
                //proccess the incoming message
                return myMessage;
            }

            private void MessageArrived(byte[] MyMessage)
            {
                //listenner will invoke this when got message
                if (OnSendToEJServer != null)
                {
                    OnSendToEJServer(Proc_MSG_In(MyMessage));
                }
            }



            #endregion


            #region linstening

            private  void StartListener()
            {   //start the listener
                if (ListenerThread == null)
                {
                    ListenerThread = new Thread(new ThreadStart(this.Listener));
                    ListenerThread.Start();
                }
                else
                {
                    try
                    {
                        ListenerThread.Resume();
                    }
                    catch
                    {
                        throw new System.Exception("Can not start ListenerThread");
                    }
                }
            }


            private void Listener()
            {
                byte[] recievBuffer;
                Encoding my_ASC = Encoding.ASCII;
                while (true)
                {
                    //listening
                    //when meeage com will invoke MsgArv

                    if (CMDLen == 0)
                    {   //use end charactar

                        byte[] ending = new byte[1];
                        ending[0] = CMDEnd;
                        string endChar = my_ASC.GetString(ending);
                        //block here
                        string ReplayStr = MySPort.ReadTo(endChar);
                        recievBuffer = my_ASC.GetBytes(ReplayStr);
                        MySPort.DiscardInBuffer();
                        Thread.Sleep(20);
                        MsgArrived.BeginInvoke(recievBuffer, null, null);
                    }
                    else
                    {
                        while (hasDataFlag == false)
                        {
                            //block here
                            Thread.Sleep(50);   //cut down the cpu usage
                        }
                        SetDataArrive(false);
                        Thread.Sleep(2);    //wait for extra byte comes
                        int BuffLen = MySPort.BytesToRead;
                        recievBuffer = new byte[BuffLen];
                        MySPort.Read(recievBuffer, 0, BuffLen);
                        MySPort.DiscardInBuffer();
                        Thread.Sleep(10);   //wait for the buffer empty ,seems not working
                        if (recievBuffer.Length < CMDLen)
                        {
                            Console.WriteLine("in:? lenth=0!!");
                            continue;
                        }

                        MsgArrived.BeginInvoke(recievBuffer, null, null);

                    }
                    string a = "";
                    for (int i = 0; i < recievBuffer.Length; i++)
                    {
                        a += recievBuffer[i].ToString("X2") + " ";
                    }
                    Console.WriteLine("in:" + a + "  lenth: " + recievBuffer.Length.ToString());

                    #region testcode
                    /*
                try
                {

                    string mytestStr;
                    mytestStr = Console.ReadLine(); //normally will use a block function
                    Encoding my_ASC = Encoding.ASCII;
                    recievBuffer = my_ASC.GetBytes(mytestStr);
                    MsgArrived.BeginInvoke(recievBuffer,null,null);
                }
                finally
                {
                    //nothing
                }
                 * */
                    #endregion

                }
            }

            #endregion

            #region property
            public string DriverName
            {
                get { return myName; }
            }

            #endregion


            #region additional serial code
            private void SetDataArrive(bool flag)
            {
                lock (LockFlag)
                {
                    hasDataFlag = flag;
                }
            }

            private void DataRecievedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
                SetDataArrive(true);
            }


            #endregion



        }
    }
